﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using DirectZZT.Shared.Core;
using Microsoft.Xna.Framework;
#endregion

namespace DirectZZT.Shared.Screens
{
    /// <summary>
    /// Manages all screens of the game.
    /// </summary>
    [DebuggerDisplay("Screens = {_screens.Count}")]
    public sealed class ScreenManager : IScreenManager
    {
        #region Fields

        private bool _started;
        private List<IScreen> _screens;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the ScreenManager class.
        /// </summary>
        private ScreenManager()
            : base()
        {
            _screens = new List<IScreen>(4);
        }

        /// <summary>
        /// Initializes a new instance of the ScreenManager class.
        /// </summary>
        /// <param name="game"></param>
        public ScreenManager(Game game)
            : this()
        {
            this.Game = game;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the resource.
        /// </summary>
        public void Initialize()
        {

        }

        /// <summary>
        /// Adds a new screen to the rendering stack, which is then immediately rendered.
        /// </summary>
        /// <param name="screen">The screen to add.</param>
        /// <param name="disposeCurrentScreen">Whether or not to dispose the current screen instead of keeping it in the stack.</param>
        public void PushScreen(IScreen screen, bool disposeCurrentScreen)
        {
            if (_screens.Count > 0)
            {
                // leave-event
                _screens.GetLast().OnLeave();
                _screens.GetLast().IsActive = false;
            }

            // dispose the current screen?
            if (disposeCurrentScreen && _screens.Count > 0)
            {
                RemoveScreen(_screens.GetLast());
            }

            // initialize the screen - first the basic initialization, then the graphical
            screen.OnAttach(this);
            _screens.Add(screen);

            // enter-event
            screen.OnEnter();
            screen.IsActive = true;

            _started = true;
        }

        /// <summary>
        /// Disposes and then removes the given screen.
        /// </summary>
        /// <param name="screen">The screen to dispose and remove.</param>
        private void RemoveScreen(IScreen screen)
        {
            screen.IsActive = false;
            screen.OnLeave();
            // disposing is done here because in Dispose() method this screen might already be vanished!
            screen.Dispose();

            _screens.Remove(screen);
            if (_screens.Count > 0)
            {
                _screens.GetLast().IsActive = true;
                _screens.GetLast().OnEnter();
            }
        }

        /// <summary>
        /// Updates the component.
        /// </summary>
        /// <param name="gameTime">The game's timing information.</param>
        public void Update(GameTime gameTime)
        {
            if (!_started)
            {
                return;
            }
            if (_screens.Count == 0)
            {
                //this.Game.Exit();
                return;
            }

            IScreen screen = _screens.GetLast();
            screen.Update(gameTime);

            if (screen.ScreenResult != ScreenResult.Stay)
            {
                // remove the screen from the stack
                RemoveScreen(_screens.GetLast());

                //// update again (for the other screen)
                //Update(gameTime);
            }
        }


        /// <summary>
        /// Draws the component.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Draw(GameTime gameTime)
        {
            if (!_started || _screens.Count == 0)
            {
                return;
            }

            // draw from first to last, every screen that will be drawn
            for (int i = 0; i < _screens.Count; i++)
            {
                IScreen screen = _screens[i];
                // draw the screen if it is either the active one, or it isn't active but shall be drawn
                if (screen.IsActive || (!screen.IsActive && screen.ForceProcessingWhileInactive))
                {
                    screen.Draw(gameTime);
                }
            }
        }

        /// <summary>
        /// Disposes all resources that were claimed by this instance.
        /// </summary>
        /// <exception cref="System.ObjectDisposedException">The object has already been disposed.</exception>
        public void Dispose()
        {
            if (!_started || _screens.Count == 0)
            {
                return;
            }

            // in case there are screens leftover, dispose and remove the screens
            while (_screens.Count > 0)
            {
                // disposing is done in the RemoveScreen method
                RemoveScreen(_screens.GetLast());
            }

            // we have done our duty, GC!
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Returns a <see cref="SlimDX.Vector2"/> that contains the percentage of the location that is stated by <paramref name="x"/> and <paramref name="y"/>.
        /// </summary>
        /// <param name="x">A value from 0 to 100 containing the percentage of the abscissa (width/x) from the left.</param>
        /// <param name="y">A value from 0 to 100 containing the percentage of the ordinate (height/x) from the top.</param>
        /// <returns>A <see cref="SlimDX.Vector2"/> that contains the percentage of the location.</returns>
        /// <exception cref="System.ArgumentException"><paramref name="x"/> or <paramref name="y"/> had values less than 0 or greater than 100.</exception>
        public Vector2 GetCoordinate(float x, float y)
        {
            if (x < 0f || x > 100f)
            {
                throw new ArgumentException("The current value is invalid. Make sure it is between 0 (zero) and 100!", "x");
            }
            if (y < 0f || y > 100f)
            {
                throw new ArgumentException("The current value is invalid. Make sure it is between 0 (zero) and 100!", "y");
            }

            float vx = (float)((640 * x) / 100);
            float yx = (float)((480 * y) / 100);
            return new Vector2(vx, yx);
        }

        /// <summary>
        /// Returns a <see cref="SlimDX.Vector2"/> that contains the percentage of the size that is stated by <paramref name="width"/> and <paramref name="width"/>.
        /// </summary>
        /// <param name="width">A value from 0 to 100 containing the percentage of the width.</param>
        /// <param name="height">A value from 0 to 100 containing the percentage of the height.</param>
        /// <returns>A <see cref="SlimDX.Vector2"/> that contains the percentage of the size.</returns>
        /// <exception cref="System.ArgumentException"><paramref name="width"/> or <paramref name="height"/> had values less than 0 or greater than 100.</exception>
        public Vector2 GetSize(float width, float height)
        {
            if (width < 0f || width > 100f)
            {
                throw new ArgumentException("The current value is invalid. Make sure it is between 0 (zero) and 100!", "width");
            }
            if (height < 0f || height > 100f)
            {
                throw new ArgumentException("The current value is invalid. Make sure it is between 0 (zero) and 100!", "height");
            }

            // TODO: algorithm!
            float vx = (float)((640 * width) / 100);
            float yx = (float)((480 * height) / 100);
            return new Vector2(vx, yx);
        }

        #endregion

        #region IScreenManager Members

        /// <summary>
        /// Gets the game this screen manager is attached to.
        /// </summary>
        public Game Game { get; private set; }

        #endregion
    }
}
